//客户端
#include <iostream>
#include <string>
#include <cstdio>
#include <cerrno>  //使用errno(错误码)
#include <cstring> //使用strerror(errno):将错误码转为字符串
#include <cstdlib> //exit()
#include <memory>  //智能指针
#include <strings.h>//bzero() :将内存块的前n个字节清零
#include <pthread.h>

#include <sys/types.h>  //是Unix/Linux系统的基本系统数据类型的头文件，socket()
#include <sys/socket.h> //socket(): 创建socket_fd ; inet_addr(): 将IP字符串转为4字节整型。
#include <netinet/in.h> //struct sockaddr_in ; inet_addr()
#include <arpa/inet.h>  //htons()、htonl() ：将主机数据转为网络数据 ; inet_addr()
#include <unistd.h>     //close(): 关闭socket_fd。

#include "log.hpp" //日志打印
#include "thread.hpp" //线程类


//两个线程都会用到sock_fd，要进行加锁吗？
//--不需要，两线程都是访问sock_fd，而不是修改sock_fd，所以不会出现数据出错的问题。
//什么时候要加锁：多线程模式下会修改临界区资源时要进行加锁。
//发送消息的线程的行动函数：
static void *udpSend(void *args)
{
    ThreadData* td = (ThreadData*)args;
    int* sock_fd = (int*)(td->sock_fd_); //获取客户端socket_fd
    struct sockaddr_in* server = (struct sockaddr_in*)(td->sockaddr_in_); //获取服务端的sockaddr_in

    std::string message;//保存要发送的信息
    while(1)
    {
        sleep(1);
        //2.1 生成消息
        std::cerr << "Please input the message that you want to send: " << std::endl;
        std::getline(std::cin, message);
        if(message == "quit" || message == "q") break;
        //2.2 发送消息
        //当客户端首次给服务端发信息时，OS会自动给客户端 bind它的IP和Port。
        sendto(*sock_fd, message.c_str(), message.size(), 0, (struct sockaddr*)server, sizeof(*server));
    }

    return nullptr;
 }

//接收消息的线程的行动函数：
static void *udpRecv(void *args)
{
    ThreadData* td = (ThreadData*)args;
    int* sock_fd = (int*)(td->sock_fd_); //获取客户端socket_fd
    struct sockaddr_in* server = (struct sockaddr_in*)(td->sockaddr_in_); //获取服务端的sockaddr_in

    char buffer[1024] = {0}; //用于接收服务端返回数据的缓冲区
    
    while(true)
    {
        //3. 从网络中接收的数据
        struct sockaddr_in temp; //用于保存发送消息方的套接字
        socklen_t len = sizeof(temp); //用于保存发送消息方的套接字大小
        ssize_t s = recvfrom(*sock_fd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&temp, &len);

        if(s > 0) //数据读取成功打印提示信息
        {
            buffer[s] = 0;
            printf("Obtain message of the server: %s\n", buffer);
        }
        else //数据读取失败打印提示信息
        {
            logMessage(FATAL, "%d:%s:%s\n", errno, strerror(errno), "创建套接字失败！"); //strerror(errno)：将错误码转为字符串。
        }
    }

}

//客户端需要服务端的IP地址和端口号才能给对方发送消息。
//多线程版：发消息和收消息应该是并发的，所以要一个线程来发消息，一个线程来收消息。
int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        logMessage(FATAL, "Usage: %s need: ip & proc.\n", argv[0]);
        exit(1);
    }
    //1.创建套接字描述符
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock_fd < 0)
    {
        logMessage(FATAL, "%d:%s:%s\n", errno, strerror(errno), "创建套接字失败！"); //strerror(errno)：将错误码转为字符串。
        exit(2);
    }
    //客户端一般不需要显示的bind指定的套接字（因为软件被用户打开时的端口号是随机的），而是让OS自动去绑定
    //客户端只需要创建一个sock_fd即可，不需要显示的创建一个sockaddr给sock_fd绑定。

    struct sockaddr_in  server;//保存服务端信息的sockaddr
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET; //服务端的协议族（套接字类型）
    server.sin_port = ntohs(atoi(argv[2]));//服务端的端口号
    server.sin_addr.s_addr = inet_addr(argv[1]); //服务端的IP地址

    
    //用智能指针，创建负责给服务端发消息的线程
    usleep(1000);
    std::unique_ptr<Thread> sender(new Thread(1, udpSend, (void*)&sock_fd, (void*)&server));
    //用智能指针，创建负责接收消息的线程
    std::unique_ptr<Thread> recver(new Thread(2, udpRecv, (void*)&sock_fd, (void*)&server));
    
    
    //主线程等待两个线程退出
    sender->p_jion();
    recver->p_jion();
    

    //1.2关闭socket_fd(文件描述符打开后要记得关闭)
    close(sock_fd);
    return 0;
}